/*

observers.js

Copyright (C) 2012 Fraunhofer Institute for Secure Information Technology

Authors: Sebastian Ruhleder <sebastian.ruhleder@sit.fraunhofer.de>
         Oliver Hoffmann <oliver.hoffmann@sit.fraunhofer.de>
		 
Contact: Andreas Poller <andreas.poller@sit.fraunhofer.de>
         Fraunhofer Institute for Secure Information Technology
		 Rheinstraße 75
		 64295 Darmstadt
		 Germany

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/gpl-3.0.txt>.

*/

/* Necessary length for token generation. */
var CONTENT_LENGTH = 30;

/*
 * Un/Like observer.
 */
var likeObserver = {
	'name': 'like',
	'event': 'click',
	'identifier': [".UFILikeLink", ".UFICommentActions > a", ".UFILikeThumb"],
	'token': function(obj, event) {
		// Set content field classes.
		var contentClasses = {
			".UFIComment": ".UFICommentBody",
			".photoUfiContainer": ".hasCaption",
			".uiStreamStory": ".userContent",
			".storyContent": ".userContent",
			".article": ".shareSubtext",
			".timelineUnitContainer": ".userContent"
		};

		// Get timestamp, if any.
		var timestamp = findTimestamp(obj, contentClasses);

		// Get content.
		var content = findRelative(obj, contentClasses);

		// Strip, if possible.
		if (stripTags(content)) content = stripTags(content);

		// If error, check if it is a picture only post.
		if (content == "error") {
			/*
			 * Timeline picture post.
			 */

			// Get attachments.
			var attachments = obj.closest("div[role=article]").find(".uiStreamAttachments");

			// Check if attachments contain image.
			if (attachments.length > 0) {
				// Use src attribute as token.
				content = attachments.find("img").attr("src");
			}

			/*
			 * Snowlift picture post.
			 */

			// Get snowlift container image.
			var snowliftContainerImage = obj.closest(".fbPhotoSnowliftContainer").find(".spotlight");

			// Check if found.
			if (snowliftContainerImage.length > 0) {
				content = snowliftContainerImage.attr("src");
			}

			/*
			 * Regular photo container.
			 */

			// Get photo container image.
			var photoContainerImage = obj.closest(".fbxPhoto").find(".fbPhotoImage");

			// Check if found.
			if (photoContainerImage.length > 0) {
				content = photoContainerImage.attr("src");
			}
		} else {
			// Use necessary length.
			content = content.substr(0, CONTENT_LENGTH);
		}

		// Add timestamp, if found.
		if (timestamp) {
			content = timestamp + content;
		}

		return content;
	},
	'fbid': function(obj, event) {
		// Find feedback parameters.
		var feedbackParameters = obj.closest('.storyInnerContent').find('input[name*=feedback]');

		// Find feedback parameters on foreign timeline
		if (feedbackParameters.length <= 0) {
			feedbackParameters = obj.closest('.fbTimelineUnit').find('input[name*=feedback]');
		}

		// Get parameter JSON.
		var parameters = feedbackParameters.attr('value');

		// Parse to object.
		parameters = $.parseJSON(parameters);

		return parameters['target_fbid'];
	},
	'meta': function(obj, event) {
		// Language map.
		var typesInLanguages = {
			// English
			'like': 'like',
			'unlike': 'unlike',
			// German
			'gefällt mir': 'like',
			'gefällt mir nicht mehr': 'unlike'
		};

		// Get Like/Unlike.
		var interaction_type = stripTags(obj.html()).toLowerCase();

		// Consider language.
		interaction_type = typesInLanguages[interaction_type];
		if (interaction_type === null) {
			interaction_type = "unknown/like/unlike";
		}

		// Get object owner.
		var objectOwnerClasses = {
			'.UFIComment': 'a.UFICommentActorName',
			'div[role=article]': '.actorName a',
			'.photoUfiContainer': '.fbPhotoContributorName a',
			'div[role=article] .uiStreamMessage': 'a:nth-child(2)',
			'.timelineUnitContainer': '.fwb',
			'.storyContent': '.passiveName'
		};
		var object_owner = stripTags(findRelative(obj, objectOwnerClasses));

		// Get location of like.
		var location = "unknown";

		// Get nearest objects.
		var timelineCapsule = obj.closest('.fbTimelineCapsule');
		var contentArea = obj.closest('#contentArea');

		if (timelineCapsule.length > 0) {
			// Check, if location is profile.
			location = "profile";

			// Check if profile belongs to user.
			var currentProfileName = $("#fbProfileCover ._8_2").html();
			var currentUserName = $(".headerTinymanName").html();
			if (currentProfileName == currentUserName) location += "self";
		} else if (contentArea.length > 0) {
			// Check, if location is news feed.
			location = "newsfeed";
		}

		// Return meta data.
		return {
			'interaction_type': interaction_type,
			'object_owner': hash(object_owner),
			'object_type': getStoryType(obj),
			'location': location
		};
	}
};

/*
 * Page Like observer.
 */
var pageLikeObserver = {
	'name': 'pagelike',
	'event': 'click',
	'identifier': [".PageLikeButton input[type=submit]"],
	'token': function(obj, event) {
		// Get name of page.
		var name = $("#fbProfileCover").find("span[itemprop=name]").html();

		// Return name as token.
		return name;
	},
	'meta': function(obj, event) {
		// Return meta data.
		return {
			'interaction_type': 'pagelike'
		};
	}
};

/*
 * Share observer.
 */
var shareObserver = {
	'name': 'share',
	'event': 'click',
	'identifier': ["form[action*=share] button[type=submit]", "form[action*=share] input[type=submit]"],
	'token': function(obj, event) {
		// Set content field classes.
		var contentClasses = {
			'form': '.UIShareStage_Summary'
		};

		// Get content.
		var content = findRelative(obj, contentClasses);

		// Use necessary length.
		content = content.substr(0, CONTENT_LENGTH);

		return stripTags(content);
	},
	'fbid': function(obj, event) {
		// FBID object.
		var fbid = null;

		// Search for fbid.
		obj.closest('div[role=dialog]').find('input[name*=attachment]').each(function() {
			if ($(this).attr('name') == 'attachment[params][1]') {
				fbid = $(this).attr('value');
			}
		});

		return fbid;
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "share";

		// Get object owner.
		var objectOwnerClasses = {
			'form': '.UIShareStage_Subtitle'
		};
		var object_owner = findRelative(obj, objectOwnerClasses);
		// Remove "By ".
		object_owner = object_owner.substr(object_owner.indexOf(" ") + 1);

		// Return meta data.
		return {
			'interaction_type': interaction_type,
			'object_owner': hash(object_owner),
			'object_type': 'status'
		};
	}
};

/*
 * Update status observer.
 */
var updateStatusObserver = {
	'name': 'updatestatus',
	'event': 'click',
	'identifier': ["form[action*=updatestatus] button[type=submit]", "form[action*=updatestatus] input[type=submit]"],
	'token': function(obj, event) {
		// Get content.
		var content = obj.closest("form[action*=updatestatus]").find("input[name=xhpc_message]").attr("value");

		return content;
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "updatestatus";

		// Privacy settings, language map.
		var privacyInLanguages = {
			// German
			'benutzerdefiniert': 'custom',
			'nur ich': 'only me',
			'freunde': 'friends',
			'öffentlich': 'public',
			// English
			'custom': 'custom',
			'only me': 'only me',
			'friends': 'friends',
			'public': 'public'
		};

		// Get privacy settings for this status update.
		var privacy = findRelative(obj, {
			"form": ".uiSelectorButton span"
		}).toLowerCase();

		// Consider language.
		privacy = privacyInLanguages[privacy];
		if (privacy === null) {
			privacy = "privacyunknown";
		}

		// Return meta data.
		return {
			'interaction_type': interaction_type,
			'privacy_scope': privacy,
			'object_type': 'status'
		};
	}
};

/*
 * Update status picture observer.
 */
var updateStatusPictureObserver = {
	'name': 'updatestatuspicture',
	'event': 'click',
	'identifier': ["form[action*=photos] button[type=submit]", "form[action*=photos] input[type=submit]"],
	'token': function(obj, event) {
		// Get content.
		var content = obj.closest("form[action*=photos]").find("textarea.mentionsTextarea").val();

		return content;
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "updatestatuspicture";

		// Return meta data.
		return {
			'interaction_type': interaction_type,
			'object_type': 'status'
		};
	}
};

/*
 * Friend accept observer.
 */
var friendAcceptObserver = {
	'name': 'friendaccepted',
	'event': 'click',
	'identifier': [".uiButtonConfirm input[name*=accept]"],
	'token': function(obj, event) {
		// Set content field classes.
		var contentClasses = {
			'.clearfix': '.title a'
		};

		// Get content.
		var content = findRelative(obj, contentClasses);

		return stripTags(content);
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "friendaccepted";

		// Return meta data.
		return {
			'interaction_type': interaction_type
		};
	}
};

/*
 * Friend ignore observer.
 */
var friendIgnoreObserver = {
	'name': 'friendignored',
	'event': 'click',
	'identifier': [".uiButton input[name*=reject]"],
	'token': function(obj, event) {
		// Set content field classes.
		var contentClasses = {
			'.clearfix': '.title a'
		};

		// Get content.
		var content = findRelative(obj, contentClasses);

		return stripTags(content);
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "friendignored";

		// Return meta data.
		return {
			'interaction_type': interaction_type
		};
	}
};

/*
 * Friend add observer.
 */
var friendAddObserver = {
	'name': 'friendadded',
	'event': 'click',
	'identifier': [".FriendRequestAdd input[type=button]"],
	'token': function(obj, event) {
		// Set content field classes.
		var contentClasses = {
			'.friendBrowserListUnit': '.title a'
		};

		// Get content.
		var content = findRelative(obj, contentClasses);

		return stripTags(content);
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "friendadded";

		// Return meta data.
		return {
			'interaction_type': interaction_type
		};
	}
};

/*
 * Unfriend observer.
 */
var unfriendObserver = {
	'name': 'unfriend',
	'event': 'click',
	'identifier': ["input[name*=remove]"],
	'token': function(obj, event) {
		// TODO: Get name of unfriended person.
		return "";
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "unfriend";

		// Return meta data.
		return {
			'interaction_type': interaction_type
		};
	}
};

/*
 * Delete status observer.
 */
var deleteStatusObserver = {
	'name': 'deletestatus',
	'event': 'click',
	'identifier': ["a[ajaxify*=remove_content]"],
	'token': function(obj, event) {
		// Get token from like observer.
		return likeObserver['token'](obj, event);
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "deletestatus";

		// Return meta data.
		return {
			'interaction_type': interaction_type
		};
	}
};

/*
 * Chat turned off observer.
 */
var chatTurnedOffObserver = {
	'name': 'chatturnoff',
	'event': 'click',
	'identifier': ["li.fbChatGoOfflineItem a"],
	'token': function(obj, event) {
		// No token necessary.
		return "";
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "chatturnoff";

		// Return meta data.
		return {
			'interaction_type': interaction_type
		};
	}
};

/*
 * Chat turned off observer.
 */
var chatActivatedObserver = {
	'name': 'chatactivated',
	'event': 'click',
	'identifier': [".fbChatGoOnlineLink"],
	'token': function(obj, event) {
		// No token necessary.
		return "";
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "chatactivated";

		// Return meta data.
		return {
			'interaction_type': interaction_type
		};
	}
};

/*
 * Hide Activity observer.
 */
var hideActivityObserver = {
	'name': 'hideactivity',
	'event': 'click',
	'identifier': ["a[ajaxify*=uninteresting]"],
	'token': function(obj, event) {
		return likeObserver['token'](obj, event);
	},
	'meta': function(obj, event) {
		// Set interaction type.
		var interaction_type = "hideactivity";

		// Get meta from like observer.
		var likeMeta = likeObserver['meta'](obj, event);

		// Return meta data.
		return {
			'interaction_type': interaction_type,
			'object_owner': likeMeta['object_owner'],
			'object_type': likeMeta['object_type']
		};
	}
};

/* Handler for open/close interactions. */

function openFacebookInteraction() {
	// Wait, until storage is initialized.
	if (!storageInitialized) {
		setTimeout(openFacebookInteraction, 100);
	} else {
		// Get last open/close interaction type.
		var lastInteraction = getLastOpenCloseInteraction();

		if (lastInteraction == "openFacebook") {
			// Get heartbeat.
			var heartbeat = getHeartbeat();

			log("DATE DIFF: " + dateDiffSeconds(heartbeat, new Date()));
			// If last heartbeat is in interval...
			if (dateDiffSeconds(heartbeat, new Date()) < OPEN_CLOSE_INTERVAL) {
				return;
			} else {
				// Otherwise, set close facebook interaction.

				// Add heartbeat delay to heartbeat.
				heartbeat.setMilliseconds(heartbeat.getMilliseconds() + HEARTBEAT_DELAY);

				// Set close facebook interaction retroactive.
				saveInteraction({
					'proband_id': getUserID(),
					'time': heartbeat,
					'interaction_type': 'closeFacebook'
				});
			}
		}

		// Still here? Save interaction.
		saveInteraction({
			'proband_id': getUserID(),
			'time': new Date(),
			'interaction_type': 'openFacebook'
		});

		// Set heartbeat;
		setHeartbeat();
	}
}

/*
 * List of observers.
 */
var observers = [
	likeObserver,
	pageLikeObserver,
	shareObserver,
	updateStatusObserver,
	updateStatusPictureObserver,
	deleteStatusObserver,
	friendAcceptObserver,
	friendIgnoreObserver,
	friendAddObserver,
	unfriendObserver,
	chatTurnedOffObserver,
	chatActivatedObserver,
	hideActivityObserver
];

/* Registers an observer to its related elements.
 *
 * Parameters:
 *     observer	- Observer
 */

function registerObserver(observer) {
	// Register all identifiers.
	$(observer['identifier'].join(", ")).each(function() {
		// Check if handle has already been added.
		if (!$(this).hasClass('rose-integrated')) {
			// Set handle class.
			$(this).addClass('rose-integrated');

			// Get event type.
			var eventType = observer['event'];

			// Register on-click functionality.
			$(this).on(eventType, function(e) {
				// Get identifier token.
				var token = hash(observer['token']($(this), e));

				// Get additional meta data.
				var meta = observer['meta']($(this), e);

				// Get fbid, if possible.
				var fbid = null;
				if (observer.hasOwnProperty('fbid')) {
					fbid = observer['fbid']($(this), e);
					// Insert fbid to meta data.
					meta['fbid'] = hash(fbid);
				}

				// Create interaction object.
				var interaction = createInteraction(token, meta);

				// Save interaction
				saveInteraction(interaction);
			});
		}
	});
}